Skip to content

Conversation

@osenan
Copy link
Contributor

@osenan osenan commented Dec 5, 2025

Pull Request

It addresses this issue #278

While the reproducibility of within function is yet not supported with side effects functions, this PR will help users to make sure their code is reproducible or receive warning instead. In further issues we can debate whether we refactor within to support linking of side effects.

To test, verify that the vignette is correctly updated, see if tests are passed.

If you create a qenv with this branch with this code, it should trigger a warning:

q <- qenv() |> within({
  set.seed(2)
  a <- runif(1)
  b <- runif(1)
})

@osenan osenan added the core label Dec 5, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Dec 5, 2025


🎉 Thank you for your contribution! Before this PR can be accepted, we require that you all read and agree to our Contributor License Agreement.
You can digitally sign the CLA by posting a comment on this Pull Request in the format shown below. This agreement will apply to this PR as well as all future contributions on this repository.


I have read the CLA Document and I hereby sign the CLA


2 out of 3 committers have signed the CLA.
✅ (llrs-roche)[https://github.com/llrs-roche]
✅ (m7pr)[https://github.com/m7pr]
@osenan
osenan seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 5, 2025

badge

Code Coverage Summary

Filename                         Stmts    Miss  Cover    Missing
-----------------------------  -------  ------  -------  -------------
R/qenv-c.R                          55       0  100.00%
R/qenv-class.R                      13       0  100.00%
R/qenv-concat.R                      7       0  100.00%
R/qenv-constructor.R                 1       0  100.00%
R/qenv-errors.R                      4       4  0.00%    6-9
R/qenv-eval_code.R                  60       1  98.33%   40
R/qenv-extract.R                    30       0  100.00%
R/qenv-get_code.R                   24       0  100.00%
R/qenv-get_env.R                     3       1  66.67%   27
R/qenv-get_messages.r                5       0  100.00%
R/qenv-get_outputs.R                 6       0  100.00%
R/qenv-get_var.R                    13       1  92.31%   13
R/qenv-get_warnings.R                5       0  100.00%
R/qenv-join.R                        1       1  0.00%    13
R/qenv-length.R                      2       1  50.00%   2
R/qenv-show.R                       29      29  0.00%    19-50
R/qenv-within.R                      8       0  100.00%
R/utils-get_code_dependency.R      265       3  98.87%   160, 258, 341
R/utils.R                           42       0  100.00%
TOTAL                              573      41  92.84%

Diff against main

Filename      Stmts    Miss  Cover
----------  -------  ------  --------
TOTAL             0       0  +100.00%

Results for commit: c16c576

Minimum allowed coverage is 80%

♻️ This comment has been updated with latest results

@github-actions
Copy link
Contributor

github-actions bot commented Dec 5, 2025

Unit Tests Summary

  1 files   14 suites   6s ⏱️
186 tests 182 ✅ 4 💤 0 ❌
270 runs  266 ✅ 4 💤 0 ❌

Results for commit 23dc660.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 5, 2025

Unit Tests Summary

  1 files   14 suites   6s ⏱️
186 tests 182 ✅ 4 💤 0 ❌
269 runs  265 ✅ 4 💤 0 ❌

Results for commit c16c576.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 5, 2025

Unit Test Performance Difference

Additional test case details
Test Suite $Status$ Time on main $±Time$ Test Case
get_outputs 💀 $0.05$ $-0.05$ explicitly_printed_object_uses_newly_registered_print_method_and_returned_as_console_output_string
get_outputs 💀 $0.04$ $-0.04$ explicitly_printed_objects_are_returned_as_console_output_string_in_a_list
get_outputs 👶 $+0.05$ get_output_explicitly_printed_object_uses_newly_registered_print_method_and_returned_as_console_output_string
get_outputs 👶 $+0.04$ get_output_explicitly_printed_objects_are_returned_as_console_output_string_in_a_list
get_outputs 👶 $+0.05$ get_output_implicitly_printed_list_is_returned_asis_even_if_its_print_is_overridden
get_outputs 👶 $+0.09$ get_output_implicitly_printed_objects_are_returned_asis_in_a_list_and_are_identical_to_ones_in_the_environment
get_outputs 👶 $+0.03$ get_output_intermediate_plots_are_not_kept
get_outputs 👶 $+0.02$ get_output_messages_are_returned_asis_in_a_list
get_outputs 👶 $+0.04$ get_output_printed_plots_are_returned_as_recordedplot_in_a_list_1_
get_outputs 👶 $+0.03$ get_output_printed_plots_are_returned_as_recordedplot_in_a_list_2_
get_outputs 👶 $+0.02$ get_output_prints_inside_for_are_bundled_together
get_outputs 👶 $+0.12$ get_output_returns_an_empty_list_if_nothing_is_printed
get_outputs 👶 $+0.02$ get_output_warnings_are_returned_asis_in_a_list
get_outputs 💀 $0.04$ $-0.04$ implicitly_printed_list_is_returned_asis_even_if_its_print_is_overridden
get_outputs 💀 $0.08$ $-0.08$ implicitly_printed_objects_are_returned_asis_in_a_list_and_are_identical_to_ones_in_the_environment
get_outputs 💀 $0.03$ $-0.03$ intermediate_plots_are_not_kept
get_outputs 💀 $0.02$ $-0.02$ messages_are_returned_asis_in_a_list
get_outputs 💀 $0.03$ $-0.03$ printed_plots_are_returned_as_recordedplot_in_a_list_1_
get_outputs 💀 $0.03$ $-0.03$ printed_plots_are_returned_as_recordedplot_in_a_list_2_
get_outputs 💀 $0.02$ $-0.02$ prints_inside_for_are_bundled_together
get_outputs 💀 $0.12$ $-0.12$ returns_an_empty_list_if_nothing_is_printed
get_outputs 💀 $0.02$ $-0.02$ warnings_are_returned_asis_in_a_list
qenv-class 💀 $0.01$ $-0.01$ creates_a_locked_environment
qenv-class 💀 $0.01$ $-0.01$ creates_a_locked_environment_when_.xData_is_manually_defined
qenv-class 💀 $0.01$ $-0.01$ initialized_qenv_s_have_different_environments
qenv-class 👶 $+0.02$ methods_new_qenv_creates_a_locked_environment
qenv-class 👶 $+0.01$ methods_new_qenv_creates_a_locked_environment_when_.xData_is_manually_defined
qenv-class 👶 $+0.01$ methods_new_qenv_initialized_qenv_s_have_different_environments
qenv-class 👶 $+0.01$ methods_new_qenv_throws_error_when_.xData_is_not_an_environment
qenv-class 💀 $0.01$ $-0.01$ throws_error_when_.xData_is_not_an_environment
qenv_constructor 💀 $0.01$ $-0.01$ does_not_allow_binding_to_be_added
qenv_constructor 💀 $0.02$ $-0.02$ does_not_allow_binding_to_be_modified
qenv_constructor 💀 $0.01$ $-0.01$ is_an_environment
qenv_constructor 💀 $0.02$ $-0.02$ ls_all.names_TRUE_show_all_objects
qenv_constructor 💀 $0.02$ $-0.02$ ls_does_not_show_hidden_objects
qenv_constructor 💀 $0.01$ $-0.01$ names_shows_available_objets
qenv_constructor 💀 $0.02$ $-0.02$ names_shows_hidden_objects
qenv_constructor 💀 $0.00$ $-0.00$ names_shows_nothing_on_empty_environment
qenv_constructor 👶 $+0.00$ parent_of_qenv_environment_is_the_parent_of_.GlobalEnv_via_qenv_directly
qenv_constructor 👶 $+0.00$ parent_of_qenv_environment_is_the_parent_of_.GlobalEnv_via_slot
qenv_constructor 👶 $+0.01$ qenv_inherits_from_environment_does_not_allow_binding_to_be_added
qenv_constructor 👶 $+0.02$ qenv_inherits_from_environment_does_not_allow_binding_to_be_modified
qenv_constructor 👶 $+0.01$ qenv_inherits_from_environment_is_an_environment
qenv_constructor 👶 $+0.03$ qenv_inherits_from_environment_ls_all.names_TRUE_show_all_objects
qenv_constructor 👶 $+0.02$ qenv_inherits_from_environment_ls_does_not_show_hidden_objects
qenv_constructor 👶 $+0.02$ qenv_inherits_from_environment_names_shows_available_objets
qenv_constructor 👶 $+0.03$ qenv_inherits_from_environment_names_shows_hidden_objects
qenv_constructor 👶 $+0.00$ qenv_inherits_from_environment_names_shows_nothing_on_empty_environment
qenv_constructor 💀 $0.00$ $-0.00$ via_qenv_directly
qenv_constructor 💀 $0.00$ $-0.00$ via_slot
qenv_get_code 👶 $+0.09$ Backticked_symbol_code_can_be_retrieved_with_get_code
qenv_get_code 👶 $+0.05$ Backticked_symbol_starting_with_underscore_is_detected_in_code_dependency
qenv_get_code 👶 $+0.04$ Backticked_symbol_with_non_native_pipe_is_detected_code_dependency
qenv_get_code 👶 $+0.04$ Backticked_symbol_with_non_native_pipe_used_as_function_is_detected_code_dependency
qenv_get_code 👶 $+0.05$ Backticked_symbol_with_space_character_is_detected_in_code_dependency
qenv_get_code 👶 $+0.05$ Backticked_symbol_without_special_characters_is_cleaned_and_detected_in_code_dependency
qenv_get_code 💀 $0.08$ $-0.08$ code_can_be_retrieved_with_get_code
qenv_get_code 💀 $0.04$ $-0.04$ detects_calls_associated_with_object_if_calls_are_separated_by_
qenv_get_code 💀 $0.04$ $-0.04$ detects_every_assign_calls_even_if_not_evaluated_if_there_is_only_one_assignment_in_this_line
qenv_get_code 💀 $0.04$ $-0.04$ does_not_break_if_code_uses_quote_
qenv_get_code 💀 $0.04$ $-0.04$ does_not_break_if_object_is_used_in_a_function_on_lhs
qenv_get_code 💀 $0.05$ $-0.05$ does_not_break_if_object_is_used_in_a_function_on_lhs_and_influencers_are_both_on_lhs_and_rhs
qenv_get_code 💀 $0.04$ $-0.04$ does_not_fall_into_a_loop
qenv_get_code 💀 $0.06$ $-0.06$ doesn_t_consider_function_called_on_the_lhs_as_a_dependent_in_this_call_dependency_in_further_calls_
qenv_get_code 💀 $0.03$ $-0.03$ extracts_code_of_a_parent_binding_but_only_those_evaluated_before_coocurence
qenv_get_code 💀 $0.03$ $-0.03$ extracts_the_code_of_a_binding_from_character_vector_containing_simple_code
qenv_get_code 💀 $0.04$ $-0.04$ extracts_the_code_of_a_parent_binding_if_used_as_an_arg_in_a_function_call
qenv_get_code 💀 $0.03$ $-0.03$ extracts_the_code_when_using_
qenv_get_code 💀 $0.02$ $-0.02$ extracts_the_code_without_downstream_usage
qenv_get_code 👶 $+0.03$ get_code_for_specific_names_detects_calls_associated_with_object_if_calls_are_separated_by_
qenv_get_code 👶 $+0.04$ get_code_for_specific_names_detects_every_assign_calls_even_if_not_evaluated_if_there_is_only_one_assignment_in_this_line
qenv_get_code 👶 $+0.04$ get_code_for_specific_names_does_not_break_if_code_uses_quote_
qenv_get_code 👶 $+0.05$ get_code_for_specific_names_does_not_break_if_object_is_used_in_a_function_on_lhs
qenv_get_code 👶 $+0.04$ get_code_for_specific_names_does_not_break_if_object_is_used_in_a_function_on_lhs_and_influencers_are_both_on_lhs_and_rhs
qenv_get_code 👶 $+0.05$ get_code_for_specific_names_does_not_fall_into_a_loop
qenv_get_code 👶 $+0.07$ get_code_for_specific_names_doesn_t_consider_function_called_on_the_lhs_as_a_dependent_in_this_call_dependency_in_further_calls_
qenv_get_code 👶 $+0.03$ get_code_for_specific_names_extracts_code_of_a_parent_binding_but_only_those_evaluated_before_coocurence
qenv_get_code 👶 $+0.03$ get_code_for_specific_names_extracts_the_code_of_a_binding_from_character_vector_containing_simple_code
qenv_get_code 👶 $+0.04$ get_code_for_specific_names_extracts_the_code_of_a_parent_binding_if_used_as_an_arg_in_a_function_call
qenv_get_code 👶 $+0.03$ get_code_for_specific_names_extracts_the_code_when_using_
qenv_get_code 👶 $+0.03$ get_code_for_specific_names_extracts_the_code_without_downstream_usage
qenv_get_code 👶 $+0.00$ get_code_for_specific_names_handles_the_code_included_in_curly_brackets
qenv_get_code 👶 $+0.05$ get_code_for_specific_names_handles_the_code_of_length_1_when_at_least_one_is_enclosed_in_curly_brackets
qenv_get_code 👶 $+0.03$ get_code_for_specific_names_handles_the_code_without_symbols_on_rhs
qenv_get_code 👶 $+0.04$ get_code_for_specific_names_returns_result_of_length_1_for_non_empty_input_and_deparse_FALSE
qenv_get_code 👶 $+0.02$ get_code_for_specific_names_warns_if_binding_doesn_t_exist_in_code
qenv_get_code 👶 $+0.01$ get_code_for_specific_names_warns_if_empty_code_slot
qenv_get_code 👶 $+0.03$ get_code_for_specific_names_works_for_names_of_length_1
qenv_get_code 💀 $0.00$ $-0.00$ handles_the_code_included_in_curly_brackets
qenv_get_code 💀 $0.05$ $-0.05$ handles_the_code_of_length_1_when_at_least_one_is_enclosed_in_curly_brackets
qenv_get_code 💀 $0.03$ $-0.03$ handles_the_code_without_symbols_on_rhs
qenv_get_code 💀 $0.04$ $-0.04$ returns_result_of_length_1_for_non_empty_input_and_deparse_FALSE
qenv_get_code 💀 $0.04$ $-0.04$ starting_with_underscore_is_detected_in_code_dependency
qenv_get_code 💀 $0.02$ $-0.02$ warns_if_binding_doesn_t_exist_in_code
qenv_get_code 💀 $0.01$ $-0.01$ warns_if_empty_code_slot
qenv_get_code 💀 $0.04$ $-0.04$ with_non_native_pipe_is_detected_code_dependency
qenv_get_code 💀 $0.04$ $-0.04$ with_non_native_pipe_used_as_function_is_detected_code_dependency
qenv_get_code 💀 $0.04$ $-0.04$ with_space_character_is_detected_in_code_dependency
qenv_get_code 💀 $0.04$ $-0.04$ without_special_characters_is_cleaned_and_detected_in_code_dependency
qenv_get_code 💀 $0.03$ $-0.03$ works_for_names_of_length_1
qenv_within 💀 $0.04$ $-0.04$ multiple_expressions
qenv_within 👶 $+0.04$ within_run_with_multiple_expressions
qenv_within 👶 $+0.02$ within_run_with_single_expression
utils-get_code_dependency 💀 $0.05$ $-0.05$ detects_
utils-get_code_dependency 💀 $0.07$ $-0.07$ detects_assign_function
utils-get_code_dependency 💀 $0.07$ $-0.07$ detects_function
utils-get_code_dependency 👶 $+0.04$ get_code_with_multiple_assignments_inside_an_expression_detects_assign_function
utils-get_code_dependency 👶 $+0.07$ get_code_with_multiple_assignments_inside_an_expression_detects_function
utils-get_code_dependency 👶 $+0.05$ get_code_with_single_assignments_inside_an_expression_detects_
utils-get_code_dependency 👶 $+0.03$ get_code_with_single_assignments_inside_an_expression_detects_assign_function

Results for commit e238907

♻️ This comment has been updated with latest results.

Copy link
Contributor

@llrs-roche llrs-roche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left a couple of comments.


While thinking about this PR I thought on a different approach to solve the issue.

What if we link any line of code on qenv not linked to any other variable (or those on side_effect_functions) are linked to all other variables?

This would require extract the code of all variables and compare it with the full code. Any expression missed for one variable is a side-effect call that could be dealt with.

library("teal.code")
q <- within(qenv(), {
  set.seed(45)
  a <- runif(1)
  b <- runif(2)
  c <- a+b
})
cat(get_code(q, names = "c"))
#> a <- runif(1)
#> b <- runif(2)
#> c <- a + b
cat(get_code(q, names = "b"))
#> b <- runif(2)
cat(get_code(q, names = "a"))
#> a <- runif(1)

Created on 2025-12-05 with reprex v2.1.1

So that the order of the detected expression is kept:

cat(get_code(q, names = "c"))
#> set.seed(45)
#> a <- runif(1)
#> b <- runif(2)
#> c <- a + b
cat(get_code(q, names = "b"))
#> set.seed(45)
#> b <- runif(2)
cat(get_code(q, names = "a"))
#> set.seed(45)
#> a <- runif(1)

@llrs-roche
Copy link
Contributor

Related to the proposal above and the opposite of the issue, some code even if not related to the variable is added when requesting code only for the variable (might be worth to open a different issue):

library("teal.code")
packageVersion("teal.code")
#> [1] '0.7.0'
q <- within(qenv(), {
  set.seed(45)
  library("lintr")
  library("lintr")
  a <- runif(1)
  b <- runif(2)
  c <- a+b
  d <- 5
})
cat(get_code(q, names = "d"))
#> library("lintr")
#> library("lintr")
#> d <- 5
cat(get_code(q, names = "c"))
#> library("lintr")
#> library("lintr")
#> a <- runif(1)
#> b <- runif(2)
#> c <- a + b
cat(get_code(q, names = "b"))
#> library("lintr")
#> library("lintr")
#> b <- runif(2)
cat(get_code(q, names = "a"))
#> library("lintr")
#> library("lintr")
#> a <- runif(1)

Created on 2025-12-05 with reprex v2.1.1

@osenan
Copy link
Contributor Author

osenan commented Dec 8, 2025

Many thanks for the comments. I have addressed two of them. The teal.gallery, I could check if some qenv has side effects functions. If it has, we could refactor the qenv to avoid warnings if they appear.
Before that, let's decide which is the best design to solve this feature. If we decide that we keep within without the option of linking functions, then this PR is the way to go. If other approach is preferred, let's close this PR and implement it.

The posiblity to add more code than necessary exists, but I only observe good code reporting in your last example (maybe I am missing something).

About the design, is it really necessary on a qenv to change the control of a random number generator more than once? Is not that we should ban it. Thus, we could create good practices in qenv that could be only to use the same side effect function once unless is very necessary. And if we define a good qenv style, we can apply specific lintr.

For the within, my pick is that either we do not allow comments and linking for the moment or we could set the side effect functions as a method parameter and they would be called with all variables as the library calls.

q <- within(qenv(), {
  a <- runif(3)
},
  side_effect {
    set.seed(2)
})
cat(get_code(q, names = "a"))
# set.seed(2)
# a <- runif(3)

@m7pr
Copy link
Contributor

m7pr commented Dec 8, 2025

My personal view is that none of the side effects should be detected automatically as this might not be intended and may lead to a lot of confusion. We have # @linksto for the purpose of side effects.

@osenan osenan force-pushed the improve-doc-within@main branch from 2b7233e to eb903f8 Compare December 9, 2025 02:02
@osenan
Copy link
Contributor Author

osenan commented Dec 9, 2025

I have read the CLA Document and I hereby sign the CLA

@osenan
Copy link
Contributor Author

osenan commented Dec 9, 2025

I have simplified the PR to include only documentation changes on function and vignette

@osenan osenan enabled auto-merge (squash) December 9, 2025 02:09
@m7pr
Copy link
Contributor

m7pr commented Dec 9, 2025

@osenan did you see this : p? #280 (comment)

@llrs-roche llrs-roche linked an issue Dec 9, 2025 that may be closed by this pull request
3 tasks
Copy link
Contributor

@m7pr m7pr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've completed the review, and similar to last time, I ended up revising almost all of the sentences for clarity and proper English structure. The content itself is excellent and technically sound, but the English phrasing was consistently difficult to follow.

I noticed that the sentence structure often seems to follow the pattern of your native language rather than standard English word order. It reads like a literal translation, which, unfortunately, doesn't account for the different grammar and flow English uses.

To help speed up this process and ensure the documentation is accessible to a wider English-speaking audience, I highly recommend using a tool like ChatGPT or another large language model (LLM) to double-check or even draft the text in English next time. These tools are excellent at refining translations to sound natural and professional in the target language.

I'm happy to keep reviewing the technical content, but using an LLM first will save both of us significant time on the language polish!

Let me know if you have any questions about my suggested changes.

osenan and others added 6 commits December 12, 2025 11:08
Co-authored-by: Marcin <133694481+m7pr@users.noreply.github.com>
Signed-off-by: Oriol Senan  <35930244+osenan@users.noreply.github.com>
Co-authored-by: Marcin <133694481+m7pr@users.noreply.github.com>
Signed-off-by: Oriol Senan  <35930244+osenan@users.noreply.github.com>
Co-authored-by: Marcin <133694481+m7pr@users.noreply.github.com>
Signed-off-by: Oriol Senan  <35930244+osenan@users.noreply.github.com>
Co-authored-by: Marcin <133694481+m7pr@users.noreply.github.com>
Signed-off-by: Oriol Senan  <35930244+osenan@users.noreply.github.com>
Co-authored-by: Marcin <133694481+m7pr@users.noreply.github.com>
Signed-off-by: Oriol Senan  <35930244+osenan@users.noreply.github.com>
Co-authored-by: Lluís Revilla <185338939+llrs-roche@users.noreply.github.com>
Signed-off-by: Oriol Senan  <35930244+osenan@users.noreply.github.com>
@osenan
Copy link
Contributor Author

osenan commented Dec 12, 2025

I have read the CLA Document and I hereby sign the CLA

@osenan
Copy link
Contributor Author

osenan commented Dec 12, 2025

@m7pr Many thanks for the review and comments. I will review better the documentation next time!

github-actions bot and others added 6 commits December 12, 2025 14:14
# Pull Request

Document expectation of what should return `qenv` with no code.
Discovered while working on
insightsengineering/teal#1630 where some code
assumed it would return `NULL`.

Previously it was only tested that
`testthat::expect_identical(get_code(qenv(), names = "a"), "")`.
It makes sense that it also return `""` when there is no code.

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Add copilot-setup-steps workflow using the reusable template from
r.pkg.template.

---------

Signed-off-by: Marcin <133694481+m7pr@users.noreply.github.com>
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
@osenan osenan merged commit 993218d into main Dec 12, 2025
25 of 27 checks passed
@osenan osenan deleted the improve-doc-within@main branch December 12, 2025 14:30
@github-actions github-actions bot locked and limited conversation to collaborators Dec 12, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Functions with side effects are not detected as dependencies

4 participants